home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1999 August / SGI Freeware 1999 August.iso / dist / fw_emacs.idb / usr / freeware / share / emacs / 19.34 / lisp / msb.el.z / msb.el
Encoding:
Text File  |  1998-10-28  |  31.8 KB  |  1,008 lines

  1. ;;; msb.el --- Customizable buffer-selection with multiple menus.
  2.  
  3. ;; Copyright (C) 1993, 1994, 1995 Lars Lindberg <Lars.Lindberg@sypro.cap.se>
  4.  
  5. ;; Author: Lars Lindberg <Lars.Lindberg@sypro.cap.se>
  6. ;; Created: 8 Oct 1993
  7. ;; Lindberg's last update version: 3.31
  8. ;; Keywords: mouse buffer menu 
  9.  
  10. ;; This file is part of GNU Emacs.
  11.  
  12. ;; GNU Emacs is free software; you can redistribute it and/or modify
  13. ;; it under the terms of the GNU General Public License as published by
  14. ;; the Free Software Foundation; either version 2, or (at your option)
  15. ;; any later version.
  16.  
  17. ;; GNU Emacs is distributed in the hope that it will be useful,
  18. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. ;; GNU General Public License for more details.
  21.  
  22. ;; You should have received a copy of the GNU General Public License
  23. ;; along with GNU Emacs; see the file COPYING.  If not, write to the
  24. ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  25. ;; Boston, MA 02111-1307, USA.
  26.  
  27. ;;; Commentary:
  28.  
  29. ;; Purpose of this package:
  30. ;;   1. Offer a function for letting the user choose buffer,
  31. ;;      not necessarily for switching to it.
  32. ;;   2. Make a better mouse-buffer-menu.
  33. ;;
  34. ;; Installation:
  35.  
  36. ;;   1. Byte compile msb first.  It uses things in the cl package that
  37. ;;      are slow if not compiled, but blazingly fast when compiled.  I
  38. ;;      have also had one report that said that msb malfunctioned when
  39. ;;      not compiled.
  40. ;;   2. (require 'msb)
  41. ;;      Note! You now use msb instead of mouse-buffer-menu.
  42. ;;   3. Now try the menu bar Buffers menu.
  43. ;;
  44. ;; Customization:
  45. ;;   Look at the variable `msb-menu-cond' for deciding what menus you
  46. ;;   want.  It's not that hard to customize, despite my not-so-good
  47. ;;   doc-string.  Feel free to send me a better doc-string.
  48. ;;   There are some constants for you to try here:
  49. ;;   msb--few-menus
  50. ;;   msb--very-many-menus (default)
  51. ;;   
  52. ;;   Look at the variable `msb-item-handling-function' for customization
  53. ;;   of the appearance of every menu item.  Try for instance setting
  54. ;;   it to `msb-alon-item-handler'.
  55. ;;   
  56. ;;   Look at the variable `msb-item-sort-function' for customization
  57. ;;   of sorting the menus.  Set it to t for instance, which means no
  58. ;;   sorting - you will get latest used buffer first.
  59. ;;
  60. ;;   Also check out the variable `msb-display-invisible-buffers-p'.
  61.  
  62. ;; Known bugs:
  63. ;; - Files-by-directory
  64. ;;   + No possibility to show client/changed buffers separately.
  65. ;;   + All file buffers only appear in in a file sub-menu, they will
  66. ;;     for instance not appear in the Mail sub-menu.
  67.  
  68. ;; Future enhancements:
  69.  
  70. ;;; Thanks goes to
  71. ;;  Mark Brader <msb@sq.com>
  72. ;;  Jim Berry <m1jhb00@FRB.GOV>
  73. ;;  Hans Chalupsky <hans@cs.Buffalo.EDU>
  74. ;;  Larry Rosenberg <ljr@ictv.com>
  75. ;;  Will Henney <will@astroscu.unam.mx>
  76. ;;  Jari Aalto <jaalto@tre.tele.nokia.fi>
  77. ;;  Michael Kifer <kifer@sbkifer.cs.sunysb.edu>
  78. ;;  Gael Marziou <gael@gnlab030.grenoble.hp.com>
  79. ;;  Dave Gillespie <daveg@thymus.synaptics.com>
  80. ;;  Alon Albert <alon@milcse.rtsg.mot.com>
  81. ;;  Kevin Broadey, <KevinB@bartley.demon.co.uk>
  82. ;;  Ake Stenhof <ake@cadpoint.se>
  83. ;;  Richard Stallman <rms@gnu.ai.mit.edu>
  84. ;;  Steve Fisk <fisk@medved.bowdoin.edu>
  85.  
  86. ;;; Code:
  87.  
  88. (require 'cl)
  89.  
  90. ;;;
  91. ;;; Some example constants to be used for `msb-menu-cond'.  See that
  92. ;;; variable for more information.  Please note that if the condition
  93. ;;; returns `multi', then the buffer can appear in several menus.
  94. ;;;
  95. (defconst msb--few-menus
  96.   '(((and (boundp 'server-buffer-clients)
  97.       server-buffer-clients
  98.       'multi)
  99.      3030
  100.      "Clients (%d)")
  101.     ((and msb-display-invisible-buffers-p
  102.       (msb-invisible-buffer-p)
  103.       'multi)
  104.      3090
  105.      "Invisible buffers (%d)")
  106.     ((eq major-mode 'dired-mode)
  107.      2010
  108.      "Dired (%d)"
  109.      msb-dired-item-handler
  110.      msb-sort-by-directory)
  111.     ((eq major-mode 'Man-mode)
  112.      4090
  113.      "Manuals (%d)")
  114.     ((eq major-mode 'w3-mode)
  115.      4020
  116.      "WWW (%d)")
  117.     ((or (memq major-mode '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
  118.      (memq major-mode '(mh-letter-mode
  119.                 mh-show-mode
  120.                 mh-folder-mode))     
  121.      (memq major-mode '(gnus-summary-mode
  122.                 news-reply-mode
  123.                 gnus-group-mode
  124.                 gnus-article-mode
  125.                 gnus-kill-file-mode
  126.                 gnus-browse-killed-mode)))
  127.      4010
  128.      "Mail (%d)")
  129.     ((not buffer-file-name)
  130.      4099
  131.      "Buffers (%d)")
  132.     ('no-multi
  133.      1099
  134.      "Files (%d)")))
  135.  
  136. (defconst msb--very-many-menus
  137.   '(((and (boundp 'server-buffer-clients)
  138.       server-buffer-clients
  139.       'multi)
  140.      1010
  141.      "Clients (%d)")
  142.     ((and (boundp 'vc-mode) vc-mode 'multi)
  143.      1020
  144.      "Version Control (%d)")
  145.     ((and buffer-file-name
  146.       (buffer-modified-p)
  147.       'multi)
  148.      1030
  149.      "Changed files (%d)")
  150.     ((and (get-buffer-process (current-buffer))
  151.       'multi)
  152.      1040
  153.      "Processes (%d)")
  154.     ((and msb-display-invisible-buffers-p
  155.       (msb-invisible-buffer-p)
  156.       'multi)
  157.      1090
  158.      "Invisible buffers (%d)") 
  159.     ((eq major-mode 'dired-mode)
  160.      2010
  161.      "Dired (%d)"
  162.      ;; Note this different menu-handler
  163.      msb-dired-item-handler
  164.      ;; Also note this item-sorter
  165.      msb-sort-by-directory)
  166.     ((eq major-mode 'Man-mode)
  167.      4030
  168.      "Manuals (%d)")
  169.     ((eq major-mode 'w3-mode)
  170.      4020
  171.      "WWW (%d)")
  172.     ((or (memq major-mode '(rmail-mode rmail-edit-mode vm-summary-mode vm-mode mail-mode))
  173.      (memq major-mode '(mh-letter-mode
  174.                 mh-show-mode
  175.                 mh-folder-mode))     
  176.      (memq major-mode '(gnus-summary-mode
  177.                 news-reply-mode
  178.                 gnus-group-mode
  179.                 gnus-article-mode
  180.                 gnus-kill-file-mode
  181.                 gnus-browse-killed-mode)))
  182.      4010
  183.      "Mail (%d)")
  184.     ;; Catchup for all non-file buffers
  185.     ((and (not buffer-file-name)
  186.       'no-multi)
  187.      4099
  188.      "Other non-file buffers (%d)")
  189.     ((and (string-match "/\\.[^/]*$" buffer-file-name)
  190.       'multi)
  191.      3090
  192.      "Hidden Files (%d)")
  193.     ((memq major-mode '(c-mode c++-mode))
  194.      3010
  195.      "C/C++ Files (%d)")
  196.     ((eq major-mode 'emacs-lisp-mode)
  197.      3020
  198.      "Elisp Files (%d)")
  199.     ((eq major-mode 'latex-mode)
  200.      3030
  201.      "LaTex Files (%d)")
  202.     ('no-multi
  203.      3099
  204.      "Other files (%d)")))
  205.  
  206. ;; msb--many-menus is obsolete
  207. (defvar msb--many-menus msb--very-many-menus)
  208.  
  209. ;;;
  210. ;;; Customizable variables
  211. ;;;
  212.  
  213. (defvar msb-separator-diff 100
  214.   "*Non-nil means use separators.
  215. The separators will appear between all menus that have a sorting key that differs by this value or more.")
  216.  
  217. (defvar msb-files-by-directory-sort-key 0
  218.   "*The sort key for files sorted by directory")
  219.  
  220. (defvar msb-max-menu-items 15
  221.   "*The maximum number of items in a menu.
  222. If this variable is set to 15 for instance, then the submenu will be split up in minor parts, 15 items each.
  223. Nil means no limit.")
  224.  
  225. (defvar msb-max-file-menu-items 10
  226.   "*The maximum number of items from different directories.
  227.  
  228. When the menu is of type `file by directory', this is the maximum
  229. number of buffers that are clumped together from different
  230. directories.
  231.  
  232. Set this to 1 if you want one menu per directory instead of clumping
  233. them together.
  234.  
  235. If the value is not a number, then the value 10 is used.")
  236.  
  237. (defvar msb-most-recently-used-sort-key -1010
  238.   "*Where should the menu with the most recently used buffers be placed?")
  239.  
  240. (defvar msb-display-most-recently-used 15
  241.   "*How many buffers should be in the most-recently-used menu.
  242.  No buffers at all if less than 1 or nil (or any non-number).")
  243.  
  244. (defvar msb-most-recently-used-title "Most recently used (%d)"
  245.   "*The title for the most-recently-used menu.")
  246.   
  247. (defvar msb-horizontal-shift-function '(lambda () 0)
  248.   "*Function that specifies a number of pixels by which the top menu should
  249. be shifted leftwards.")
  250.  
  251. (defvar msb-display-invisible-buffers-p nil
  252.   "*Show invisible buffers or not.
  253. Non-nil means that the buffer menu should include buffers that have
  254. names that starts with a space character.")
  255.  
  256. (defvar msb-item-handling-function 'msb-item-handler
  257.   "*The appearance of a buffer menu.
  258.  
  259. The default function to call for handling the appearance of a menu
  260. item.  It should take to arguments, BUFFER and MAX-BUFFER-NAME-LENGTH,
  261. where the latter is the max length of all buffer names.
  262.  
  263. The function should return the string to use in the menu.
  264.  
  265. When the function is called, BUFFER is the current buffer.
  266. This function is called for items in the variable `msb-menu-cond' that
  267. have nil as ITEM-HANDLING-FUNCTION.  See `msb-menu-cond' for more
  268. information.")
  269.  
  270. (defvar msb-item-sort-function 'msb-sort-by-name
  271.   "*The order of items in a buffer menu.
  272. The default function to call for handling the order of items in a menu
  273. item.  This function is called like a sort function.  The items
  274. look like (ITEM-NAME . BUFFER).
  275. ITEM-NAME is the name of the item that will appear in the menu.
  276. BUFFER is the buffer, this is not necessarily the current buffer.
  277.  
  278. Set this to nil or t if you don't want any sorting (faster).")
  279.  
  280. (defvar msb-files-by-directory nil
  281.   "*Non-nil means that files should be sorted by directory instead of
  282. the groups in msb-menu-cond.")
  283.  
  284. (defvar msb-menu-cond msb--very-many-menus
  285.   "*List of criteria for splitting the mouse buffer menu.
  286. The elements in the list should be of this type:
  287.  (CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLING-FN ITEM-SORT-FN).
  288.  
  289. When making the split, the buffers are tested one by one against the
  290. CONDITION, just like a lisp cond: When hitting a true condition, the
  291. other criteria are *not* tested and the buffer name will appear in
  292. the menu with the menu-title corresponding to the true condition.
  293.  
  294. If the condition returns the symbol `multi', then the buffer will be
  295. added to this menu *and* tested for other menus too.  If it returns
  296. `no-multi', then the buffer will only be added if it hasn't been added
  297. to any other menu.
  298.  
  299. During this test, the buffer in question is the current buffer, and
  300. the test is surrounded by calls to `save-excursion' and
  301. `save-match-data'.
  302.  
  303. The categories are sorted by MENU-SORT-KEY.  Smaller keys are on
  304. top.  nil means don't display this menu.
  305.  
  306. MENU-TITLE is really a format.  If you add %d in it, the %d is replaced
  307. with the number of items in that menu.
  308.  
  309. ITEM-HANDLING-FN, is optional.  If it is supplied and is a
  310. function, than it is used for displaying the items in that particular
  311. buffer menu, otherwise the function pointed out by
  312. `msb-item-handling-function' is used.
  313.  
  314. ITEM-SORT-FN, is also optional.
  315. If it is not supplied, the function pointed out by
  316. `msb-item-sort-function' is used.
  317. If it is nil, then no sort takes place and the buffers are presented
  318. in least-recently-used order.
  319. If it is t, then no sort takes place and the buffers are presented in
  320. most-recently-used order.
  321. If it is supplied and non-nil and not t than it is used for sorting
  322. the items in that particular buffer menu.
  323.  
  324. Note1: There should always be a `catch-all' as last element,
  325. in this list.  That is an element like (t TITLE ITEM-HANDLING-FUNCTION).
  326. Note2: A buffer menu appears only if it has at least one buffer in it.
  327. Note3: If you have a CONDITION that can't be evaluated you will get an
  328. error every time you do \\[msb].")
  329.  
  330. (defvar msb-after-load-hooks nil
  331.   "Hooks to be run after the msb package has been loaded.")
  332.  
  333. ;;;
  334. ;;; Internal variables
  335. ;;;
  336.  
  337. ;; The last calculated menu.
  338. (defvar msb--last-buffer-menu nil)
  339.  
  340. ;; If this is non-nil, then it is a string that describes the error.
  341. (defvar msb--error nil)
  342.  
  343. ;;;
  344. ;;; Some example function to be used for `msb-item-handling-function'.
  345. ;;;
  346. (defun msb-item-handler (buffer &optional maxbuf)
  347.   "Create one string item, concerning BUFFER, for the buffer menu.
  348. The item looks like:
  349. *% <buffer-name>
  350. The `*' appears only if the buffer is marked as modified.
  351. The `%' appears only if the buffer is read-only.
  352. Optional second argument MAXBUF is completely ignored."
  353.   (let ((name (buffer-name))
  354.     (modified (if (buffer-modified-p) "*" " "))
  355.     (read-only (if buffer-read-only "%" " ")))
  356.     (format "%s%s %s" modified read-only name)))
  357.  
  358.  
  359. (eval-when-compile (require 'dired))
  360.  
  361. ;; `dired' can be called with a list of the form (directory file1 file2 ...)
  362. ;; which causes `dired-directory' to be in the same form.
  363. (defun msb--dired-directory ()
  364.   (cond ((stringp dired-directory)
  365.      (abbreviate-file-name (expand-file-name dired-directory)))
  366.     ((consp dired-directory)
  367.      (abbreviate-file-name (expand-file-name (car dired-directory))))
  368.     (t
  369.      (error "Unknown type of `dired-directory' in buffer %s"
  370.         (buffer-name)))))
  371.  
  372. (defun msb-dired-item-handler (buffer &optional maxbuf)
  373.   "Create one string item, concerning a dired BUFFER, for the buffer menu.
  374. The item looks like:
  375. *% <buffer-name>
  376. The `*' appears only if the buffer is marked as modified.
  377. The `%' appears only if the buffer is read-only.
  378. Optional second argument MAXBUF is completely ignored."
  379.   (let ((name (msb--dired-directory))
  380.     (modified (if (buffer-modified-p) "*" " "))
  381.     (read-only (if buffer-read-only "%" " ")))
  382.     (format "%s%s %s" modified read-only name)))
  383.  
  384. (defun msb-alon-item-handler (buffer maxbuf)
  385.   "Create one string item for the buffer menu.
  386. The item looks like:
  387. <buffer-name> *%# <file-name>
  388. The `*' appears only if the buffer is marked as modified.
  389. The `%' appears only if the buffer is read-only.
  390. The `#' appears only version control file (SCCS/RCS)."
  391.   (format (format "%%%ds  %%s%%s%%s  %%s" maxbuf)
  392.           (buffer-name buffer)
  393.           (if (buffer-modified-p) "*" " ")
  394.           (if buffer-read-only "%" " ")
  395.           (if (and (boundp 'vc-mode) vc-mode) "#" " ")
  396.           (or buffer-file-name "")))
  397.  
  398. ;;;
  399. ;;; Some example function to be used for `msb-item-sort-function'.
  400. ;;;
  401. (defun msb-sort-by-name (item1 item2)
  402.   "Sorts the items depending on their buffer-name
  403. An item look like (NAME . BUFFER)."
  404.   (string-lessp (buffer-name (cdr item1))
  405.         (buffer-name (cdr item2))))
  406.  
  407.  
  408. (defun msb-sort-by-directory (item1 item2)
  409.   "Sorts the items depending on their directory.  Made for dired.
  410. An item look like (NAME . BUFFER)."
  411.   (string-lessp (save-excursion (set-buffer (cdr item1)) (msb--dired-directory))
  412.         (save-excursion (set-buffer (cdr item2)) (msb--dired-directory))))
  413.  
  414. ;;;
  415. ;;; msb
  416. ;;;
  417. ;;; This function can be used instead of (mouse-buffer-menu EVENT)
  418. ;;; function in "mouse.el".
  419. ;;; 
  420. (defun msb (event)
  421.   "Pop up several menus of buffers for selection with the mouse.
  422. This command switches buffers in the window that you clicked on, and
  423. selects that window.
  424.  
  425. See the function `mouse-select-buffer' and the variable
  426. `msb-menu-cond' for more information about how the menus are split."
  427.   (interactive "e")
  428.   (let ((old-window (selected-window))
  429.     (window (posn-window (event-start event))))
  430.     (unless (framep window) (select-window window))
  431.     (let ((buffer (mouse-select-buffer event)))
  432.       (if buffer
  433.       (switch-to-buffer buffer)
  434.     (select-window old-window))))
  435.   nil)
  436.  
  437. ;;;
  438. ;;; Some supportive functions
  439. ;;;
  440. (defun msb-invisible-buffer-p (&optional buffer)
  441.   "Return t if optional BUFFER is an \"invisible\" buffer.
  442. If the argument is left out or nil, then the current buffer is considered."
  443.   (and (> (length (buffer-name buffer)) 0)
  444.        (eq ?\ (aref (buffer-name buffer) 0))))
  445.  
  446. ;; Strip one hierarchy level from the end of PATH.
  447. (defun msb--strip-path (path)
  448.   (save-match-data
  449.     (if (string-match "\\(.+\\)/[^/]+$" path)
  450.     (substring path (match-beginning 1) (match-end 1))
  451.       "/")))
  452.  
  453. ;; Create an alist with all buffers from LIST that lies under the same
  454. ;; directory will be in the same item as the directory string as
  455. ;; ((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K BUFFER-K+1...)) ...)
  456. (defun msb--init-file-alist (list)
  457.   (let ((buffer-alist
  458.      (sort (mapcan
  459.         (function
  460.          (lambda (buffer)
  461.            (let ((file-name (buffer-file-name buffer)))
  462.              (when file-name
  463.                (list (cons (msb--strip-path file-name) buffer))))))
  464.         list)
  465.            (function (lambda (item1 item2)
  466.                (string< (car item1) (car item2)))))))
  467.     ;; Make alist that looks like
  468.     ;; ((PATH1 . (BUFFER-1 BUFFER-2 ...)) (PATH2 . (BUFFER-K)) ...)
  469.     (let ((path nil)
  470.       (buffers nil)
  471.       (result nil))
  472.       (append
  473.        (mapcan (function
  474.            (lambda (item)
  475.          (cond
  476.           ((and path
  477.             (string= path (car item)))
  478.            (push (cdr item) buffers)
  479.            nil)
  480.           (t
  481.            (when path
  482.              (setq result (cons path buffers)))
  483.            (setq path (car item))
  484.            (setq buffers (list (cdr item)))
  485.            (and result (list result))))))
  486.           buffer-alist)
  487.        (list (cons path buffers))))))
  488.  
  489. ;; Choose file-menu with respect to directory for every buffer in LIST.
  490. (defun msb--choose-file-menu (list)
  491.   (let ((buffer-alist (msb--init-file-alist list))
  492.     (final-list nil)
  493.     (max-clumped-together (if (numberp msb-max-file-menu-items)
  494.                   msb-max-file-menu-items
  495.                 10))
  496.     (top-found-p nil)
  497.     (last-path nil)
  498.     first rest path buffers)
  499.     (setq first (car buffer-alist))
  500.     (setq rest (cdr buffer-alist))
  501.     (setq path (car first))
  502.     (setq buffers (cdr first))
  503.     (while rest
  504.       (let ((found-p nil)
  505.         (tmp-rest rest)
  506.         new-path item)
  507.     (setq item (car tmp-rest))
  508.     (while (and tmp-rest
  509.             (<= (length buffers) max-clumped-together)
  510.             (>= (length (car item)) (length path))
  511.             (string= path (substring (car item) 0 (length path))))
  512.       (setq found-p t)
  513.       (setq buffers (append buffers (cdr item)))
  514.       (setq tmp-rest (cdr tmp-rest))
  515.       (setq item (car tmp-rest)))
  516.     (cond
  517.      ((> (length buffers) max-clumped-together)
  518.       (setq last-path (car first))
  519.       (setq first
  520.         (cons (format (if top-found-p
  521.                   "%s/... (%d)"
  522.                 "%s (%d)")
  523.                   (car first)
  524.                   (length (cdr first)))
  525.               (cdr first)))
  526.       (setq top-found-p nil)
  527.       (push first final-list)
  528.       (setq first (car rest)
  529.         rest (cdr rest))
  530.       (setq path (car first)
  531.         buffers (cdr first)))
  532.      (t
  533.       (when found-p
  534.         (setq top-found-p t)
  535.         (setq first (cons path buffers)
  536.           rest tmp-rest))
  537.       (setq path (msb--strip-path path)
  538.         buffers (cdr first))
  539.       (when (and last-path
  540.              (or (and (>= (length path) (length last-path))
  541.                   (string= last-path
  542.                        (substring path 0 (length last-path))))
  543.              (and (< (length path) (length last-path))
  544.                   (string= path
  545.                        (substring last-path 0 (length path))))))
  546.              
  547.         (setq first
  548.           (cons (format (if top-found-p
  549.                     "%s/... (%d)"
  550.                   "%s (%d)")
  551.                 (car first)
  552.                 (length (cdr first)))
  553.             (cdr first)))
  554.         (setq top-found-p nil)
  555.         (push first final-list)
  556.         (setq first (car rest)
  557.           rest (cdr rest))
  558.         (setq path (car first)
  559.         buffers (cdr first)))))))
  560.     (setq first
  561.       (cons (format (if top-found-p
  562.                 "%s/... (%d)"
  563.               "%s (%d)")
  564.             (car first)
  565.             (length (cdr first)))
  566.         (cdr first)))
  567.     (setq top-found-p nil)
  568.     (push first final-list)
  569.     (nreverse final-list)))
  570.  
  571. ;; Create a vector as:
  572. ;; [BUFFER-LIST-VARIABLE CONDITION MENU-SORT-KEY MENU-TITLE ITEM-HANDLER SORTER)
  573. ;; from an element in `msb-menu-cond'.  See that variable for a
  574. ;; description of its elements.
  575. (defun msb--create-function-info (menu-cond-elt)
  576.   (let* ((list-symbol (make-symbol "-msb-buffer-list"))
  577.      (tmp-ih (and (> (length menu-cond-elt) 3)
  578.               (nth 3 menu-cond-elt)))
  579.      (item-handler (if (and tmp-ih (fboundp tmp-ih))
  580.                tmp-ih
  581.              msb-item-handling-function))
  582.      (tmp-s (if (> (length menu-cond-elt) 4)
  583.             (nth 4 menu-cond-elt)
  584.           msb-item-sort-function))
  585.      (sorter (if (or (fboundp tmp-s)
  586.              (null tmp-s)
  587.              (eq tmp-s t))
  588.             tmp-s
  589.            msb-item-sort-function)))
  590.     (when (< (length menu-cond-elt) 3)
  591.       (error "Wrong format of msb-menu-cond."))
  592.     (when (and (> (length menu-cond-elt) 3)
  593.            (not (fboundp tmp-ih)))
  594.       (signal 'invalid-function (list tmp-ih)))
  595.     (when (and (> (length menu-cond-elt) 4)
  596.            tmp-s
  597.            (not (fboundp tmp-s))
  598.            (not (eq tmp-s t)))
  599.       (signal 'invalid-function (list tmp-s)))
  600.     (set list-symbol ())
  601.     (vector list-symbol            ;BUFFER-LIST-VARIABLE
  602.         (nth 0 menu-cond-elt)    ;CONDITION
  603.         (nth 1 menu-cond-elt)    ;SORT-KEY
  604.         (nth 2 menu-cond-elt)    ;MENU-TITLE
  605.         item-handler        ;ITEM-HANDLER
  606.         sorter)            ;SORTER
  607.     ))
  608.  
  609. ;; This defsubst is only used in `msb--choose-menu' below.  It was
  610. ;; pulled out merely to make the code somewhat clearer.  The indention
  611. ;; level was too big.
  612. (defsubst msb--collect (function-info-vector)
  613.   (let ((result nil)
  614.     (multi-flag nil)
  615.     function-info-list)
  616.     (setq function-info-list
  617.       (loop for fi
  618.         across function-info-vector
  619.         if (and (setq result
  620.                   (eval (aref fi 1))) ;Test CONDITION
  621.             (not (and (eq result 'no-multi)
  622.                   multi-flag))
  623.             (progn (when (eq result 'multi)
  624.                  (setq multi-flag t))
  625.                    t))
  626.         collect fi
  627.         until (and result
  628.                (not (eq result 'multi)))))
  629.     (when (and (not function-info-list)
  630.            (not result))
  631.       (error "No catch-all in msb-menu-cond!"))
  632.     function-info-list))
  633.  
  634. ;; Adds BUFFER to the menu depicted by FUNCTION-INFO
  635. ;; All side-effects.  Adds an element of form (BUFFER-TITLE . BUFFER)
  636. ;; to the buffer-list variable in function-info.
  637. (defun msb--add-to-menu (buffer function-info max-buffer-name-length)
  638.   (let ((list-symbol (aref function-info 0))) ;BUFFER-LIST-VARIABLE
  639.     ;; Here comes the hairy side-effect!
  640.     (set list-symbol
  641.      (cons (cons (funcall (aref function-info 4) ;ITEM-HANDLER
  642.                   buffer
  643.                   max-buffer-name-length)
  644.              buffer)
  645.            (eval list-symbol)))))
  646.   
  647. ;; Selects the appropriate menu for BUFFER.
  648. ;; This is all side-effects, folks!
  649. ;; This should be optimized.
  650. (defsubst msb--choose-menu (buffer function-info-vector max-buffer-name-length)
  651.   (unless (and (not msb-display-invisible-buffers-p)
  652.            (msb-invisible-buffer-p buffer))
  653.     (condition-case nil
  654.     (save-excursion
  655.       (set-buffer buffer)
  656.       ;; Menu found.  Add to this menu
  657.       (mapc (function
  658.          (lambda (function-info)
  659.            (msb--add-to-menu buffer function-info max-buffer-name-length)))
  660.         (msb--collect function-info-vector)))
  661.       (error (unless msb--error
  662.            (setq msb--error
  663.              (format
  664.               "In msb-menu-cond, error for buffer `%s'."
  665.               (buffer-name buffer)))
  666.            (error "%s" msb--error))))))
  667.  
  668. ;; Return (SORT-KEY TITLE . BUFFER-LIST) or nil if the
  669. ;; buffer-list is empty.
  670. (defun msb--create-sort-item (function-info)
  671.   (let ((buffer-list (eval (aref function-info 0))))
  672.     (when buffer-list
  673.       (let ((sorter (aref function-info 5)) ;SORTER
  674.         (sort-key (aref function-info 2))) ;MENU-SORT-KEY
  675.     (when sort-key
  676.       (cons sort-key    
  677.         (cons (format (aref function-info 3) ;MENU-TITLE
  678.                   (length buffer-list))
  679.               (cond
  680.                ((null sorter)
  681.             buffer-list)
  682.                ((eq sorter t)
  683.             (nreverse buffer-list))
  684.                (t
  685.             (sort buffer-list sorter))))))))))
  686.  
  687. ;; Returns a list on the form ((TITLE . BUFFER-LIST)) for
  688. ;; the most recently used buffers.
  689. (defun msb--most-recently-used-menu (max-buffer-name-length)
  690.   (when (and (numberp msb-display-most-recently-used)
  691.           (> msb-display-most-recently-used 0))
  692.     (let* ((buffers (cdr (buffer-list)))
  693.        (most-recently-used
  694.         (loop with n = 0
  695.           for buffer in buffers
  696.           if (save-excursion
  697.                (set-buffer buffer)
  698.                (and (not (msb-invisible-buffer-p))
  699.                 (not (eq major-mode 'dired-mode))))
  700.           collect (save-excursion
  701.                 (set-buffer buffer)
  702.                 (cons (funcall msb-item-handling-function
  703.                        buffer
  704.                        max-buffer-name-length)
  705.                   buffer))
  706.           and do (incf n)
  707.           until (>= n msb-display-most-recently-used))))
  708.       (cons (if (stringp msb-most-recently-used-title)
  709.         (format msb-most-recently-used-title
  710.             (length most-recently-used))
  711.           (signal 'wrong-type-argument (list msb-most-recently-used-title)))
  712.         most-recently-used))))
  713.  
  714. (defun msb--create-buffer-menu-2 ()
  715.   (let ((max-buffer-name-length 0)
  716.     file-buffers
  717.     function-info-vector)
  718.     ;; Calculate the longest buffer name.
  719.     (mapc
  720.      (function
  721.       (lambda (buffer)
  722.     (if (or msb-display-invisible-buffers-p
  723.         (not (msb-invisible-buffer-p)))
  724.         (setq max-buffer-name-length
  725.           (max max-buffer-name-length
  726.                (length (buffer-name buffer)))))))
  727.      (buffer-list))
  728.     ;; Make a list with elements of type
  729.     ;; (BUFFER-LIST-VARIABLE
  730.     ;;  CONDITION
  731.     ;;  MENU-SORT-KEY
  732.     ;;  MENU-TITLE
  733.     ;;  ITEM-HANDLER
  734.     ;;  SORTER)
  735.     ;; Uses "function-global" variables:
  736.     ;; function-info-vector
  737.     (setq function-info-vector
  738.       (apply (function vector)
  739.          (mapcar (function msb--create-function-info)
  740.              msb-menu-cond)))
  741.     ;; Split the buffer-list into several lists; one list for each
  742.     ;; criteria.  This is the most critical part with respect to time.
  743.     (mapc (function (lambda (buffer)
  744.               (cond ((and msb-files-by-directory
  745.                   (buffer-file-name buffer))
  746.                  (push buffer file-buffers))
  747.                 (t
  748.                  (msb--choose-menu buffer
  749.                            function-info-vector
  750.                            max-buffer-name-length)))))
  751.       (buffer-list))
  752.     (when file-buffers
  753.       (setq file-buffers
  754.         (mapcar (function
  755.              (lambda (buffer-list)
  756.                (cons msb-files-by-directory-sort-key
  757.                  (cons (car buffer-list)
  758.                    (sort
  759.                     (mapcar (function
  760.                          (lambda (buffer)
  761.                            (cons (save-excursion
  762.                                (set-buffer buffer)
  763.                                (funcall msb-item-handling-function
  764.                                   buffer
  765.                                   max-buffer-name-length))
  766.                              buffer)))
  767.                         (cdr buffer-list))
  768.                     (function
  769.                      (lambda (item1 item2)
  770.                        (string< (car item1) (car item2)))))))))
  771.              (msb--choose-file-menu file-buffers))))
  772.     ;; Now make the menu - a list of (TITLE . BUFFER-LIST)
  773.     (let* (menu
  774.        (most-recently-used
  775.         (msb--most-recently-used-menu max-buffer-name-length))
  776.        (others (append file-buffers
  777.                (loop for elt
  778.                  across function-info-vector
  779.                  for value = (msb--create-sort-item elt)
  780.                  if value collect value))))
  781.       (setq menu
  782.         (mapcar 'cdr        ;Remove the SORT-KEY
  783.             ;; Sort the menus - not the items.
  784.             (msb--add-separators
  785.             (sort
  786.              ;; Get a list of (SORT-KEY TITLE . BUFFER-LIST)
  787.              ;; Also sorts the items within the menus.
  788.              (if (cdr most-recently-used)
  789.              (cons
  790.               ;; Add most recent used buffers
  791.               (cons msb-most-recently-used-sort-key
  792.                 most-recently-used)
  793.               others)
  794.                others)
  795.              (function (lambda (elt1 elt2)
  796.                  (< (car elt1) (car elt2))))))))
  797.       ;; Now make it a keymap menu
  798.       (append
  799.        '(keymap "Select Buffer")
  800.        (msb--make-keymap-menu menu)
  801.        (when msb-separator-diff
  802.      (list (list 'separator "---")))
  803.        (list (cons 'toggle 
  804.            (cons
  805.            (if msb-files-by-directory
  806.                "*Files by type*"
  807.              "*Files by directory*")
  808.            'msb--toggle-menu-type)))))))
  809.  
  810. (defun msb--create-buffer-menu  ()
  811.   (save-match-data
  812.     (save-excursion
  813.       (msb--create-buffer-menu-2))))
  814.  
  815. ;;;
  816. ;;; Multi purpose function for selecting a buffer with the mouse.
  817. ;;; 
  818. (defun msb--toggle-menu-type ()
  819.   (interactive)
  820.   (setq msb-files-by-directory (not msb-files-by-directory))
  821.   (menu-bar-update-buffers))
  822.  
  823. (defun mouse-select-buffer (event)
  824.   "Pop up several menus of buffers, for selection with the mouse.
  825. Returns the selected buffer or nil if no buffer is selected.
  826.  
  827. The way the buffers are split is conveniently handled with the
  828. variable `msb-menu-cond'."
  829.   ;; Popup the menu and return the selected buffer.
  830.   (when (or msb--error
  831.         (not msb--last-buffer-menu)
  832.         (not (fboundp 'frame-or-buffer-changed-p))
  833.         (frame-or-buffer-changed-p))
  834.     (setq msb--error nil)
  835.     (setq msb--last-buffer-menu (msb--create-buffer-menu)))
  836.   (let ((position event)
  837.     choice)
  838.     (when (and (fboundp 'posn-x-y)
  839.            (fboundp 'posn-window))
  840.       (let ((posX (car (posn-x-y (event-start event))))
  841.         (posY (cdr (posn-x-y (event-start event))))
  842.         (posWind (posn-window (event-start event))))
  843.     ;; adjust position
  844.     (setq posX (- posX (funcall msb-horizontal-shift-function))
  845.           position (list (list posX posY) posWind))))
  846.     ;; This `sit-for' magically makes the menu stay up if the mouse
  847.     ;; button is released within 0.1 second.
  848.     (sit-for 0 100)
  849.     ;; Popup the menu
  850.     (setq choice (x-popup-menu position msb--last-buffer-menu))
  851.     (cond
  852.      ((eq (car choice) 'toggle)
  853.       ;; Bring up the menu again with type toggled.
  854.       (msb--toggle-menu-type)
  855.       (mouse-select-buffer event))
  856.      ((and (numberp (car choice))
  857.        (null (cdr choice)))
  858.       (let ((msb--last-buffer-menu (nthcdr 3 (assq (car choice) msb--last-buffer-menu))))
  859.     (mouse-select-buffer event)))
  860.      ((while (numberp (car choice))
  861.     (setq choice (cdr choice))))
  862.      ((and (stringp (car choice))
  863.        (null (cdr choice)))
  864.       (car choice))
  865.      ((null choice)
  866.       choice)
  867.      (t
  868.       (error "Unknown form for buffer: %s" choice)))))
  869.             
  870. ;; Add separators
  871. (defun msb--add-separators (sorted-list)
  872.   (cond
  873.    ((or (not msb-separator-diff)
  874.     (not (numberp msb-separator-diff)))
  875.     sorted-list)
  876.    (t
  877.     (let ((last-key nil))
  878.       (mapcan
  879.        (function
  880.     (lambda (item)
  881.       (cond
  882.        ((and msb-separator-diff
  883.          last-key 
  884.          (> (- (car item) last-key)
  885.             msb-separator-diff))
  886.         (setq last-key (car item))
  887.         (list (cons last-key 'separator)
  888.           item))
  889.        (t
  890.         (setq last-key (car item))
  891.         (list item)))))
  892.        sorted-list)))))
  893.  
  894. (defun msb--split-menus-2 (list mcount result)
  895.   (cond
  896.    ((> (length list) msb-max-menu-items)
  897.     (let ((count 0)
  898.       sub-name
  899.       (tmp-list nil))
  900.       (while (< count msb-max-menu-items)
  901.     (push (pop list) tmp-list)
  902.     (incf count))
  903.     (setq tmp-list (nreverse tmp-list))
  904.     (setq sub-name (concat (car (car tmp-list)) "..."))
  905.     (push (append (list mcount sub-name
  906.             'keymap sub-name)
  907.           tmp-list)
  908.       result))
  909.     (msb--split-menus-2 list (1+ mcount) result))
  910.    ((null result)
  911.     list)
  912.    (t
  913.     (let (sub-name)
  914.       (setq sub-name (concat (car (car list)) "..."))
  915.       (push (append (list mcount sub-name
  916.             'keymap sub-name)
  917.           list)
  918.       result))
  919.     (nreverse result))))
  920.     
  921. (defun msb--split-menus (list)
  922.  (msb--split-menus-2 list 0 nil))
  923.  
  924.  
  925. (defun msb--make-keymap-menu (raw-menu)
  926.   (let ((end (cons '(nil) 'menu-bar-select-buffer))
  927.     (mcount 0))
  928.     (mapcar
  929.      (function
  930.       (lambda (sub-menu)
  931.     (cond 
  932.      ((eq 'separator sub-menu)
  933.       (list 'separator "---"))
  934.      (t
  935.       (let ((buffers (mapcar (function
  936.                   (lambda (item)
  937.                     (let ((string (car item))
  938.                       (buffer (cdr item)))
  939.                       (cons (buffer-name buffer)
  940.                         (cons string end)))))
  941.                  (cdr sub-menu))))
  942.         (append (list (incf mcount) (car sub-menu)
  943.               'keymap (car sub-menu))
  944.             (msb--split-menus buffers)))))))
  945.      raw-menu)))
  946.  
  947. (defun menu-bar-update-buffers (&optional arg)
  948.   ;; If user discards the Buffers item, play along.
  949.   (when (and (lookup-key (current-global-map) [menu-bar buffer])
  950.          (or (not (fboundp 'frame-or-buffer-changed-p))
  951.          (frame-or-buffer-changed-p)
  952.          arg))
  953.     (let ((frames (frame-list))
  954.       buffers-menu frames-menu)
  955.       ;; Make the menu of buffers proper.
  956.       (setq msb--last-buffer-menu (msb--create-buffer-menu))
  957.       (setq buffers-menu msb--last-buffer-menu)
  958.       ;; Make a Frames menu if we have more than one frame.
  959.       (when (cdr frames)
  960.     (let* ((frame-length (length frames))
  961.            (f-title  (format "Frames (%d)" frame-length)))
  962.       ;; List only the N most recently selected frames
  963.       (when (and (integerp msb-max-menu-items)
  964.              (>  msb-max-menu-items 1)
  965.              (> frame-length msb-max-menu-items))
  966.         (setcdr (nthcdr msb-max-menu-items frames) nil))
  967.       (setq frames-menu
  968.         (nconc
  969.          (list 'frame f-title '(nil) 'keymap f-title)
  970.          (mapcar
  971.           (function
  972.            (lambda (frame)
  973.              (nconc
  974.               (list frame
  975.                 (cdr (assq 'name
  976.                        (frame-parameters frame)))
  977.                 (cons nil nil))
  978.               'menu-bar-select-frame)))
  979.           frames)))))
  980.       (define-key (current-global-map) [menu-bar buffer]
  981.     (cons "Buffers"
  982.           (if (and buffers-menu frames-menu)
  983.           ;; Combine Frame and Buffers menus with separator between
  984.           (nconc (list 'keymap "Buffers and Frames" frames-menu
  985.                    (and msb-separator-diff '(separator "---")))
  986.              (cddr buffers-menu))
  987.         (or buffers-menu 'undefined)))))))
  988.  
  989. (when (and (boundp 'menu-bar-update-hook)
  990.        (not (fboundp 'frame-or-buffer-changed-p)))
  991.   (defvar msb--buffer-count 0)
  992.   (defun frame-or-buffer-changed-p ()
  993.     (let ((count (length (buffer-list))))
  994.       (when (/= count msb--buffer-count)
  995.         (setq msb--buffer-count count)
  996.         t))))
  997.  
  998. (unless (or (not (boundp 'menu-bar-update-hook))
  999.         (memq 'menu-bar-update-buffers menu-bar-update-hook))
  1000.     (add-hook 'menu-bar-update-hook 'menu-bar-update-buffers))
  1001.  
  1002. (and (fboundp 'mouse-buffer-menu)
  1003.      (substitute-key-definition 'mouse-buffer-menu 'msb (current-global-map)))
  1004.  
  1005. (provide 'msb)
  1006. (eval-after-load 'msb (run-hooks 'msb-after-load-hooks))
  1007. ;;; msb.el ends here
  1008.